package com.nulldev.util.internal.backport.httpclient_rw.impl.websocket;

import java.net.ProxySelector;
import java.net.URI;
import java.time.Duration;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

import com.nulldev.util.internal.backport.concurrency9.Lists;
import com.nulldev.util.internal.backport.concurrency9.concurrent.CompletableFuture;
import com.nulldev.util.internal.backport.httpclient_rw.HttpClient;
import com.nulldev.util.internal.backport.httpclient_rw.WebSocket;
import com.nulldev.util.internal.backport.httpclient_rw.WebSocket.Builder;
import com.nulldev.util.internal.backport.httpclient_rw.WebSocket.Listener;
import com.nulldev.util.internal.backport.httpclient_rw.impl.common.Pair;
import com.nulldev.util.internal.backport.optionals.Optional;

import static com.nulldev.util.internal.backport.concurrency9.Objects.requireNonNull;
import static com.nulldev.util.internal.backport.httpclient_rw.impl.common.Pair.pair;

public final class BuilderImpl implements Builder {

	private final HttpClient client;
	private URI uri;
	private Listener listener;
	private final Optional<ProxySelector> proxySelector;
	private final Collection<Pair<String, String>> headers;
	private final Collection<String> subprotocols;
	private Duration timeout;

	public BuilderImpl(HttpClient client, ProxySelector proxySelector) {
		this(client, null, null, Optional.ofNullable(proxySelector), new LinkedList<>(), new LinkedList<>(), null);
	}

	private BuilderImpl(HttpClient client, URI uri, Listener listener, Optional<ProxySelector> proxySelector, Collection<Pair<String, String>> headers,
			Collection<String> subprotocols, Duration timeout) {
		this.client = client;
		this.uri = uri;
		this.listener = listener;
		this.proxySelector = proxySelector;
		// If a proxy selector was supplied by the user, it should be present
		// on the client and should be the same that what we got as an argument
		assert !client.proxy().isPresent() || client.proxy().equals(proxySelector);
		this.headers = headers;
		this.subprotocols = subprotocols;
		this.timeout = timeout;
	}

	@Override
	public Builder header(String name, String value) {
		requireNonNull(name, "name");
		requireNonNull(value, "value");
		headers.add(pair(name, value));
		return this;
	}

	@Override
	public Builder subprotocols(String mostPreferred, String... lesserPreferred) {
		requireNonNull(mostPreferred, "mostPreferred");
		requireNonNull(lesserPreferred, "lesserPreferred");
		List<String> subprotocols = new LinkedList<>();
		subprotocols.add(mostPreferred);
		for (int i = 0; i < lesserPreferred.length; i++) {
			String p = lesserPreferred[i];
			requireNonNull(p, "lesserPreferred[" + i + "]");
			subprotocols.add(p);
		}
		this.subprotocols.clear();
		this.subprotocols.addAll(subprotocols);
		return this;
	}

	@Override
	public Builder connectTimeout(Duration timeout) {
		this.timeout = requireNonNull(timeout, "timeout");
		return this;
	}

	@Override
	public CompletableFuture<WebSocket> buildAsync(URI uri, Listener listener) {
		this.uri = requireNonNull(uri, "uri");
		this.listener = requireNonNull(listener, "listener");
		// A snapshot of builder inaccessible for further modification
		// from the outside
		BuilderImpl copy = immutableCopy();
		return WebSocketImpl.newInstanceAsync(copy);
	}

	HttpClient getClient() {
		return client;
	}

	URI getUri() {
		return uri;
	}

	Listener getListener() {
		return listener;
	}

	Collection<Pair<String, String>> getHeaders() {
		return headers;
	}

	Collection<String> getSubprotocols() {
		return subprotocols;
	}

	Duration getConnectTimeout() {
		return timeout;
	}

	Optional<ProxySelector> getProxySelector() {
		return proxySelector;
	}

	private BuilderImpl immutableCopy() {
		@SuppressWarnings(
			{ "unchecked" })
		BuilderImpl copy = new BuilderImpl(client, uri, listener, proxySelector, Lists.of(this.headers.toArray(new Pair[0])),
				Lists.of(this.subprotocols.toArray(new String[0])), timeout);
		return copy;
	}
}
